import json
import os
from typing import Union, Dict

from _hellchin_webrun.src.core.engine.action_result import ActionResult
from _hellchin_webrun.src.core.engine.action_step import ActionStep
from _hellchin_webrun.src.core.engine.models import ConfigModel, SuiteModel

from _hellchin_webrun.src.core.utils.logger.save_log import LoggerHandler


class Runner:
    """测试执行器"""

    def __init__(self, config: Union[Dict, ConfigModel], suite: Union[Dict, SuiteModel]):
        """
        初始化测试执行器

        Args:
            config (dict/ConfigModel): 配置信息
            suite (dict/SuiteModel): 测试套件
        """
        self.config = ConfigModel.model_dump(config) if isinstance(config, dict) else config
        self.suite = SuiteModel.model_dump(suite) if isinstance(suite, dict) else suite

        # 初始化浏览器、上下文、PageObject对象
        self.browser = None  # 浏览器对象
        self.context = None  # 上下文信息
        self.page = None  # PageObject对象

        # 记录测试套件的日志
        self.suit_log = LoggerHandler()
        self.result = ActionResult(config=config.model_dump(), suite=suite.model_dump())

    def run(self):
        """执行的入口函数"""
        # 记录测试套件的开始
        self.result.suite_run_start()

        try:
            # 1、执行测试套件的公共前置步骤证
            self.run_suite_setup()
            # 2、执行测试套件中的用例
            self.run_suite()
            # 3、关闭浏览器
            if hasattr(self, 'page') and self.page:
                self.page.close()
        except Exception as e:
            pass

        # 记录测试套件的结束
        self.result.suite_run_end(getattr(self.suit_log, 'log_data'))

        return self.result.get_result()

    def run_suite_setup(self):
        """执行测试套件的公共前置步骤"""
        try:
            if self.suite.setup_step:

                self.suit_log.info(f'测试套件: {self.suite.name}')

                # 获取套件的公共前置步骤
                suite_setup_steps = self.suite.setup_step

                # 遍历前置步骤，执行前置步骤
                step_action = ActionStep(config=self.config, log_obj=self.suit_log)
                for step in suite_setup_steps:
                    # TestLogger.success.step("前置步骤", step.desc)
                    self.suit_log.info(f'前置步骤: {step.desc}')
                    step_action.perform(step)

                # 保存前置步骤创建的浏览器对象
                self.browser = step_action.browser
                self.context = step_action.context
                self.page = step_action.page
            else:
                self.suit_log.info('测试套件没有公共前置步骤')
        except Exception as e:
            self.suit_log.error(f'执行套件公共前置步骤失败')
            self.suit_log.error(e)
            raise e

    def run_suite(self):
        """执行测试套件"""

        # 遍历用例，执行用例
        cases = self.suite.cases

        if not cases:
            self.suit_log.warning('测试套件没有用例')
            return

        # 遍历套件中所有的用例
        for case in cases:
            # 判断用例是否被跳过
            if case.skip:
                # TestLogger.skip.step("跳过用例", case.name)
                self.suit_log.info(f'跳过用例：{case.name}')
                continue

            # 捕获异常，并打印错误信息
            else:

                self.suit_log.info(f'测试用例: {case.name}')
                # 执行用例
                self.run_case(case)
            # 捕获断言错误

    def run_case(self, case):
        """执行单条用例"""

        # 获取用例的执行步骤
        steps = case.steps

        # 记录单条用例的日志
        case_log_obj = LoggerHandler()

        # 传入配置、浏览器、上下文、PageObject对象，创建ActionStep对象
        step_action = ActionStep(self.config, self.browser, self.context, self.page, log_obj=case_log_obj)

        # 使用with语句，创建case_logger对象,并使用它作为上下文管理器,在执行用例时打印前后日志
        self.suit_log.info(f'开始执行用例【{case.name}】')

        try:
            # 遍历执行步骤
            for step in steps:
                # TestLogger.success.step("执行步骤", step.desc)
                case_log_obj.info(f'执行步骤: {step.desc}')
                step_action.perform(step)

        except AssertionError as e:
            # 保存断言失败的截图
            img = step_action.save_page_img(f"{case.name}_fail")
            self.result.add_fail(case.model_dump(), getattr(case_log_obj, 'log_data'), img)
            self.suit_log.error(f'执行步骤断言失败，失败原因: {e}')
        except Exception as e:
            # 保存失败的截图
            img = step_action.save_page_img(f"{case.name}_fail")
            self.result.add_fail(case.model_dump(), getattr(case_log_obj, 'log_data'), img)
            self.suit_log.error(f'执行步骤失败: {e}')
        else:
            # 保存成功的截图
            img = step_action.save_page_img(f"{case.name}_success")
            self.result.add_success(case.model_dump(), getattr(case_log_obj, 'log_data'), img)
            self.suit_log.info(f'执行用例【{case.name}】成功')

        # 保存前置步骤创建的浏览器对象，这个开了就代表可以使用前面用例的浏览器
        self.browser = step_action.browser
        self.context = step_action.context
        self.page = step_action.page
